/*
         LEGENDA CODICI:
        
        #################################################################################### 
        #                             #                      #                             #
        #         TIPO AVVISO         #        BUZZER        #             LED             #     
        #                             #                      #                             #
        ####################################################################################          
        # Modalità registrazione      # Beep singolo rapido  # Led verde acceso fisso      #
        #                             #                      #                             #
        # Modalità eliminazione       # Beep singolo rapido  # Led rosso acceso fisso      #
        #                             #                      #                             #
        # Tessera già registrara      # Beep singolo rapido  # Led rosso singolo lampeggio #
        #                             #                      #                             #
        # Tessera registrata          # Beep singolo veloce  # Led verde 5 lampeggi veloci #
        #                             #                      #                             #
        # Tessera già cancellata      # 10 Beep molto rapidi # Led rosso 10 lampeggi rapidi#
        #                             #                      #                             #
        # Tessera cancellata          # Beep singolo lento   # Led verde singolo lampeggio #
        #                             #                      #                             #
        # Tessera non corretta        # Beep doppio veloce   # Led rosso singolo lampeggio #
        #                             #                      #                             #
        # Tessera corretta            # Beep singolo lento   # Led verde singolo lampeggio #
        #                             #                      #                             #
        # Memoria cancellata totale   # Beep singolo lungo   # Led rosso e verde accesi poi#
        #                             #                      # beep e lampeggio rapido     #
        #                             #                      #                             #
        # Checksum non corretto       # Beep singolo veloce  # Led rosso singolo lampeggio #
        ####################################################################################
*/
                       



#include <EEPROM.h>
#include <SoftwareSerial.h>

#define PULSANTE   5               //pin relativo al pulsante da premere per entrare in modalità scrittura/cancellazione

#define DATA_PIN       7           //scegliere il pin che si vuole utilizzare in base alla scelta fatta con il jumper sulla scheda (7 | 8)
#define RELE_PIN       9           //scegliere il pin che si vuole utilizzare in base alla scelta fatta con il jumper sulla scheda (9 | 10)
#define BUZZ_PIN       3           //scegliere il pin che si vuole utilizzare in base alla scelta fatta con il jumper sulla scheda (3 | 11)
#define GREEN_LED_PIN  8           //pin relativo al led verde
#define RED_LED_PIN    6           //pin relativo al led rosso

//scegliere cosa fare quando viene letta una scheda

#define RELE     1                //scegliere '1' per fare in modo che alla lettura di una scheda il relè venga attivato '0' per non fare nulla
#define BUZZER   1                 //scegliere '1' per fare in modo che alla lettura di una scheda il buzzer emetta un suono '0' per non fare nulla
#define LED      1                 //scegliere '1' per fare in modo che alla lettura di una scheda corretta venga acceso il led verde e per una scheda incorretta il led rosso '0' per non fare nulla

#define DURATA_RELE 10000          //scegliere il tempo per il quale deve rimanere acceso il relè (se viene inserito '0' il relè funzionerà in modo bistabile)

boolean check;                    //variabile con la quale eseguo tutti i controlli all'interno dello sketch
int on_off=0;                     //variabile che utilizzo per controllare lo stato del led in modalità bistabile

SoftwareSerial mySerial(DATA_PIN,1);    //inizializzo il pin sul quale leggere i dati trasmessi dall'ID-12
void setup() {
  
  if(DURATA_RELE>60000)                                                    //controllo che il tempo impostato per la durata di attivazione del relè sia inferiore a 1 minuto
    while(1){                                                              //in caso contrario stampo su seriale un messaggio di errore in un ciclo infinito
        delay(2000);
        Serial.print("Tempo relè non valido, troppo alto");
    }
  
  pinMode(PULSANTE,INPUT);                                                 //imposto il pin del pulsante in modalità input per verificare quando il pulsante viene premuto
  digitalWrite(PULSANTE,HIGH);                                             //e lo setto alto, in modo tale da attivare la resistenza di pull-up
  
  if(RELE)                                                                 //controllo se è stato scelto di attivare o meno il relè, nel primo caso, imposto il pin assegnatogli come output
    pinMode(RELE_PIN,OUTPUT);
  if(BUZZER)                                                               //controllo se è stato scelto di attivare o meno il buzzer, nel primo caso, imposto il pin assegnatogli come output
    pinMode(BUZZ_PIN,OUTPUT);
  if(LED){                                                                 //controllo se è stato scelto di attivare o meno i led, nel primo caso, imposto i pin assegnatogli come output
    pinMode(GREEN_LED_PIN,OUTPUT);
    pinMode(RED_LED_PIN,OUTPUT);
  }
  Serial.begin(9600);                                                      //Inizializzo la porta seriale sulla frequenza di 9600 baud
  mySerial.begin(9600);                                                    //inizializzo la seriale sulla quale leggo i dati delle schede a 9600 baud
  
  if(digitalRead(PULSANTE)==LOW) azzera();                                 //controllo che il il pulsante sia premuto in fase di accensione del dispositivo, in caso affermativo azzero tutta la memoria EEPROM
}

void loop () {
  
  byte val;                                                          //variabile che utilizzo per leggere i valori dalla tessera appena passata
  byte code[6];                                                      //vettore nel quale salvo il codice letto completo
  byte checksum;                                                     //variabile sulla quale calcolo e salvo il checksum
  byte bytesread;                                                    //variabile che viene utilizzata per per contare quanti byte sono stati letti
  byte tempbyte;                                                     //variabile che mi serve per memorizzare temporaneamente mezzo byte letto
  
  unsigned long int tempo=0;                                         //variabile che utilizzo per salvare il tempo attuale, per contare i millisecondi passati 
  boolean scrivere=false;                                            //variabile che utilizzo per controllare se la tessera appena letta è da salvare o da controllare
  boolean controllo=false;                                           //variabile che utilizzo per controllare se la tessera appena letta è da cancellare oppure no

  if(digitalRead(PULSANTE)==LOW){                                    //controllo se il pulsante è premuto
     tempo=millis();                                                 //se lo è salvo gli attuali millisecondi passati dall'avvio del dispositivo
     while((digitalRead(PULSANTE)==LOW)&&(tempo+3000>millis()));     //quindi mando in esecuzione un ciclo che non fa nulla
     if(millis()>tempo+2999){                                        //controllo dopo la fine del ciclo se esso è stato in esecuzione per 3 secondi, confrontando il tempo iniziale + 3000 con il tempo attuale 
       if(LED)  
         digitalWrite(GREEN_LED_PIN,HIGH);                           //se così è, accendo il led verde
         Serial.println("Modalità registrazione");                   //e stampo sulla seriale che sono entrato in modalità registrazione
       if(BUZZER){
         analogWrite(BUZZ_PIN,50);                                   
         delay(50);                                                  //e faccio fare un suono di avviso al buzzer
         digitalWrite(BUZZ_PIN,LOW);
       }
         scrivere=true;                                              //e pongo a vero la variabile scrivere
     }
     if(digitalRead(PULSANTE)==LOW){                                 //se dopo ciò il pulsante è ancora premuto 
       while((digitalRead(PULSANTE)==LOW)&&(tempo+5000>millis()));   //mando in esecuzione un altro ciclo nullo
       if(millis()>tempo+4999){                                      //se esso è stato in esecuzione per 2 secondi significa che sono entrato in modalita eliminazione
         Serial.println("Modalità eliminazione");                    //quindi lo scrivo sulla seriale
        if(LED){
         digitalWrite(RED_LED_PIN,HIGH);                             //accendo il led rosso
         digitalWrite(GREEN_LED_PIN,LOW);                            //spengo quello verde, precedentemente acceso
        }
        if(BUZZER){
         analogWrite(BUZZ_PIN,50);                                    
         delay(50);                                                  //faccio fare un suono di avviso al buzzer
         digitalWrite(BUZZ_PIN,LOW);
        }
         while(digitalRead(PULSANTE)==LOW);                          //mando in esecuzione un ciclo finchè il pulsante non viene rilasciato
         controllo=true;                                             //e pongo a vero la variabile controllo
       }
     }
  }
  
//-------------------------------------------------------------------------------------inizio do-while------------------------------------------------------------------------------------------
  
  do{                                                                //inizio un ciclo che finirà solo quando verrà premuto nuovamente il pulsante
    
   val = 0;                                                          
   checksum = 0;                                                     //azzero le variabili precedentemente dichiarate
   bytesread = 0;
   tempbyte = 0;
  
  if(mySerial.available() > 0) {                                     //controllo se sulla mia seriale è presente qualche dato
    if((val = mySerial.read()) == 2) {                               //se così è leggo da essa il valore
      bytesread = 0;                                                 //e se è uguale a 2 (carattere di controllo) pongo a 0 la variabile bytesread
      while (bytesread < 12) {                                       //mando in esecuzione un ciclo per 12 volte, in modo da poter leggere tutti i 12 caratteri della tessera (5 byte del codice + 1 del cehcksum
      if( mySerial.available() > 0) {                                //controllo se i dati sono disponibili ad essere letti
          val = mySerial.read();                                     //quindi assegno a 'val' il valore dell'i-esimo carattere
          if((val == 0x0D)||(val == 0x0A)||(val == 0x03)||(val == 0x02)) {     //se leggo un carattere 'header' o un carattere di stop 
            break;                                                             // fermo la lettura
          }

          
          if ((val >= '0') && (val <= '9')) {
            val -= '0';
          }                                                                    //traduco in esadecimale il carattere appena letto
          else if ((val >= 'A') && (val <= 'F')) {
            val = 10 + val - 'A';
          }

          //ogni 2 caratteri letti, aggiungo il byte così creato al vettore 'code'
          
          if (bytesread & 1 == 1) {                                            //se ho letto un solo carattere fin'ora
            code[bytesread >> 1] = (val | (tempbyte << 4));                        //assegno alla seconda parte del byte in posizione bytesread-esima il valore esadecimale del carattere letto

            if (bytesread >> 1 != 5) {                                             //se ho letto l'ultimo byte della scheda calcolo il checksum
              checksum ^= code[bytesread >> 1];                                    //facendo la XOR sull'ultimo byte letto
            };
          } else {
            tempbyte = val;                                                    //altrimenti assegno il valore letto alla variabile tempbyte 
          };

          bytesread++;                                                        //mi preparo a leggere il prossimo byte
        } 
      }

     if (bytesread == 12) {                                                   //se ho letto tutti i 6 byte    
        (code[5]==checksum) ? check = true : check = false ;                  //controllo che il checksum sia corretto
        if(check){                                                            //se lo è passo a controllare se devo salvare o cancellare
            check=false;                                                      //rimetto a false la variabile check per successivi utilizzi
            if(scrivere&&!controllo){                                         //controllo se devo scrivere 
                  for(int i=0;i<1021;i+=5){                                   //in caso affermativo eseguo un ciclo che controlla tutta la EEPROM
                        if((EEPROM.read(i)==code[0])&&(EEPROM.read(i+1)==code[1])&&(EEPROM.read(i+2)==code[2])&&(EEPROM.read(i+3)==code[3])&&(EEPROM.read(i+4)==code[4])){  
                            check=true;                                        //se trovo il codice della tessera letta già salvato nella EEPROM metto a true la variabile 'check'                       
                            break;                                             //ed esco dal ciclo
                        }
                  }  
                  if(check){                                                   //quindi controllo il valore della variabile check, se è vero, significa che la tessera è già stata registrata
                        Serial.print("Tessera già registrata!");               //quindi lo comunico su seriale
                        stampa_code(code);                                    
                      if(LED)
                        digitalWrite(RED_LED_PIN,HIGH);
                        delay(50);
                      if(BUZZER)
                        analogWrite(BUZZ_PIN,50);                              //e mando un segnale di errore luminoso e/o sonoro in sempre in base al fatto che l'utente abbia specificato led e buzzer
                        delay(50);
                      if(BUZZER)
                        digitalWrite(BUZZ_PIN,LOW);
                      if(LED)
                        digitalWrite(RED_LED_PIN,LOW);
                  }
                                                
                        
                  else{                                                        //se la tessera non è stata trovata
                      check=false;                                             //rimetto a false la variabile check per successivi utilizzi
                      for(int i=0;i<1021;i+=5){                                //quindi eseguo un ciclo che controlla tutta la EEPROM in cerca di 5 byte successivi liberi
                        if((EEPROM.read(i)==0xff)&&(EEPROM.read(i+1)==0xff)&&(EEPROM.read(i+2)==0xff)&&(EEPROM.read(i+3)==0xff)&&(EEPROM.read(i+4)==0xff)){
                          for(int j=i;j<i+5;j++)                               //una volta trovati, partendo dal primo, fino al quinto, ci salvo il valore della tessera
                              EEPROM.write(j,code[j-i]);                             //eseguendo un ciclo 5 volte
                          check=true;                                                //pongo a true la variabile check
                          break;                                               //ed esco dal ciclo
                        }  
                      }
                      if(check){                                               //se la variabile check è vera, significa che ho salvato con successo, quindi 
                          Serial.print("Tessera Salvata");                     //lo stampo su seriale
                          stampa_code(code);
                        if(BUZZER){
                          analogWrite(BUZZ_PIN,50);
                          delay(100);
                          digitalWrite(BUZZ_PIN,LOW);
                        }
                        if(LED){
                          for(int i=0;i<5;i++){
                           digitalWrite(GREEN_LED_PIN,HIGH);                   //e mando un segnale luminoso e/o sonoro in sempre in base al fatto che l'utente abbia specificato led e buzzer
                           delay(50);
                           digitalWrite(GREEN_LED_PIN,LOW);
                           delay(50);
                          }
                          digitalWrite(GREEN_LED_PIN,HIGH);
                        }
                      }
                      else{                                                    //se la variabile check non è vera, significa che ho controllato tutta la memoria senza trovare 5 byte sequenziali liberi
                           Serial.println("Memoria piena");                    //quindi spamo su seriale che la memoria è piena 
                           for(int i=0;i<5;i++){
                           if(LED)
                             digitalWrite(RED_LED_PIN,HIGH);
                           if(BUZZER)
                             analogWrite(BUZZ_PIN,50);                      
                           delay(50);                                          //e mando un segnale di errore luminoso e/o sonoro in sempre in base al fatto che l'utente abbia specificato led e buzzer
                           if(LED)
                             digitalWrite(RED_LED_PIN,LOW);
                           if(BUZZER)
                             digitalWrite(BUZZ_PIN,LOW);
                           delay(50);
                          }
                    }
                }
            }
            else if(scrivere&&controllo){                                      //se non bisogna salvare, controllo se bisogna eliminare una tessera
                  int posizione=-1;                                            //quindi inizializzo a -1 la variabile posizione, che mi servirà per salvare la posizione nella EEPROM della tessera
                  for(int i=0;i<1021;i+=5){                                    //ed eseguo un ciclo che controlla tutta la EEPROM per cercare il codice corrispondente
                        if((EEPROM.read(i)==code[0])&&(EEPROM.read(i+1)==code[1])&&(EEPROM.read(i+2)==code[2])&&(EEPROM.read(i+3)==code[3])&&(EEPROM.read(i+4)==code[4])){ 
                            posizione=i;                                       //se viene trovato salvo la posizione del primo byte nella variabile posizione 
                        break;                                                 //ed esco dal ciclo
                        }
                  }
                  if(posizione!=-1){                                           //quindi controllo che la variabile posizione sia diversa da -1 così da sapere se è stato trovato o meno il codice
                      for(int j=posizione;j<posizione+5;j++)                   //eseguo quindi un ciclo partendo dalla posizione 'posizione' nella EEPROM
                              EEPROM.write(j,0xff);                            //sovrascrivendo i 5 byte corrispondenti alla tessera, con il byte di default '0xff'
                      Serial.print("Scheda cancellata");                       //una volta fatto ciò, stampo su seriale l'avvenuta cancellazione
                      stampa_code(code);  
                      if(LED){
                        digitalWrite(GREEN_LED_PIN,HIGH);
                        digitalWrite(RED_LED_PIN,HIGH);
                      }
                      if(BUZZER)
                        analogWrite(BUZZ_PIN,50);                              //e mando un segnale luminoso e/o sonoro in sempre in base al fatto che l'utente abbia specificato led e buzzer
                      delay(250);
                      if(LED)
                        digitalWrite(GREEN_LED_PIN,LOW);
                      if(BUZZER)
                        digitalWrite(BUZZ_PIN,LOW);
                  }
                  else{                                                        //se la variabile posizione vale -1 significa che non ha trovato in memoria la tessera letta
                      Serial.print("Impossibile cancellare la scheda, non è salvata");  //quindi lo comunico su seriale
                      stampa_code(code);  
                      for(int x=0;x<10;x++){
                        if(LED)
                          digitalWrite(RED_LED_PIN,HIGH);
                        if(BUZZER)
                          analogWrite(BUZZ_PIN,50);
                        delay(25);                                             //e mando un segnale di errore luminoso e/o sonoro in sempre in base al fatto che l'utente abbia specificato led e buzzer
                        if(LED)
                          digitalWrite(RED_LED_PIN,LOW);
                        if(BUZZER)
                          digitalWrite(BUZZ_PIN,LOW);
                        delay(25);
                      }
                      if(LED)
                        digitalWrite(RED_LED_PIN,HIGH);
                    }
                                   
            }
   
            check=true;                                                        //rimetto a vero il valore della variabile check siccome il checksum è corretto
            }
            else{                                                              //se il checksum fosse incorretto
                 Serial.print("Checksum incorretto");                          //lo comunico su seriale
                 for(int i=0;i<3;i++){                                
                    if(LED)
                      digitalWrite(RED_LED_PIN,HIGH);
                    if(BUZZER)
                      analogWrite(BUZZ_PIN,50);
                    delay(30);                                                 //e mando un segnale di errore luminoso e/o sonoro in sempre in base al fatto che l'utente abbia specificato led e buzzer
                    if(LED)
                      digitalWrite(RED_LED_PIN,LOW);
                    if(BUZZER)
                      digitalWrite(BUZZ_PIN,LOW);
                 }
                 if(LED)
                    digitalWrite(RED_LED_PIN,HIGH);
            }
        
     }
    }
  }
  }
  while((digitalRead(PULSANTE)==HIGH)&&(controllo||scrivere));
  
//-------------------------------------------------------------------------------------------fine do-while---------------------------------------------------------------------------------------
  
  if(LED){
    digitalWrite(GREEN_LED_PIN,LOW);                                          //spengo gli eventuali led accesi per conoscere la modalità nella quale ero all'interno del ciclo
    digitalWrite(RED_LED_PIN,LOW);  
  }
  
  if (bytesread == 12) {                                                      //controllo di avere letto tutti i 6 byte della tessera
     if(check){                                                               //controllo che il checksum sia corretto
           if(!scrivere){                                                     //e controllo anche che non ci sia da salvare/scrivere una tessera
                  check=false;                                                //rimetto a false la variabile check per successivi utilizzi
                  for(int i=0;i<1021;i+=5)                                    //eseguo un ciclo che controlla tutta la EEPROM alla ricerca della tessera letta
                        if(EEPROM.read(i)==code[0]&&EEPROM.read(i+1)==code[1]&&EEPROM.read(i+2)==code[2]&&EEPROM.read(i+3)==code[3]&&EEPROM.read(i+4)==code[4]){
                            check=true;                                       //se viene trovata metto a true la variabile check 
                            break;                                            //ed esco dal ciclo
                        }
                     
                     if(check){                                               //quindi controllo il valore della variabile check
                       Serial.print("Tessera valida");                        //se è vero, significa che la tessera è stata trovata e quindi è valida, e lo stampo su seriale
                       stampa_code(code);   
                       if(LED)
                            digitalWrite(GREEN_LED_PIN,HIGH);
                       if(BUZZER){
                            analogWrite(BUZZ_PIN,50);
                            delay(200);
                            digitalWrite(BUZZ_PIN,LOW);                       //quindi in base alla selezione dell'utente     
                       }                                                      //mando un segnale luminoso e/o sonoro
                       if(RELE){                                              //in più accendo il relè
                            if(DURATA_RELE){                                  //secondo la modalità impostata dall'utente
                               digitalWrite(RELE_PIN,HIGH);
                               tempo=millis();
                               while(tempo+DURATA_RELE>millis());
                               digitalWrite(RELE_PIN,LOW);
                            }
                            else{
                               if(on_off){
                                  digitalWrite(RELE_PIN,LOW);
                                  on_off--;
                               }
                               else{
                                  digitalWrite(RELE_PIN,HIGH);
                                  on_off++;
                               }
                            }                       
                       }  
                    }
                      else{                                                    //se al contrario il valore è falso
                        Serial.print("Tessera non valida!");                   //significa che ho controllato tutta la memoria senza trovare la tessera, quindi lo comunico su seriale
                        stampa_code(code);
                        if(LED)
                            digitalWrite(RED_LED_PIN,HIGH); 
                        if(BUZZER){
                            analogWrite(BUZZ_PIN,50);
                            delay(50);
                            digitalWrite(BUZZ_PIN,LOW);                        //e mando un segnale di errore luminoso e/o sonoro in sempre in base al fatto che l'utente abbia specificato led e buzzer
                            delay(50);
                            analogWrite(BUZZ_PIN,50);
                            delay(50);
                            digitalWrite(BUZZ_PIN,LOW);
                        }
                     }
                    }
                    if(LED){
                      delay(500);  
                      digitalWrite(GREEN_LED_PIN,LOW);                         //spegno gli eventuali led rimasti accesi
                      digitalWrite(RED_LED_PIN,LOW);    
                    }
     }     
     else{                                                                     //se il checksum fosse incorretto
                 Serial.print("Checksum incorretto");                          //lo comunico su seriale
                 for(int i=0;i<3;i++){                                
                    if(LED)
                      digitalWrite(RED_LED_PIN,HIGH);
                    if(BUZZER)
                      analogWrite(BUZZ_PIN,50);
                    delay(30);                                                 //e mando un segnale di errore luminoso e/o sonoro in sempre in base al fatto che l'utente abbia specificato led e buzzer
                    if(LED)
                      digitalWrite(RED_LED_PIN,LOW);
                    if(BUZZER)
                      digitalWrite(BUZZ_PIN,LOW);
                 }
     }
    }  
  bytesread=0;                                                                 //azzero la variabile bytesread per una prossima lettura
}

//--------------------------------------------------------------------------FUNZIONE PER AZZERARE LA MEMORIA EEPROM------------------------------------------------------------------------------

void azzera(){
  if(LED){
    digitalWrite(GREEN_LED_PIN,HIGH); 
    digitalWrite(RED_LED_PIN,HIGH); 
  }
  for(int i=0;i<1023;i++)
      EEPROM.write(i,0xff);
  Serial.println("Memoria Azzerata!");
  if(BUZZER)
    analogWrite(BUZZ_PIN,50);
  for(int i=0;i<5;i++) 
    if(LED){  
      digitalWrite(GREEN_LED_PIN,HIGH);
      delay(100);
      digitalWrite(GREEN_LED_PIN,LOW);
      digitalWrite(RED_LED_PIN,HIGH);
      delay(100);
      digitalWrite(RED_LED_PIN,LOW);
    }
  if(BUZZER)
    digitalWrite(BUZZ_PIN,LOW);
}

//--------------------------------------------------------------FUNZIONE PER STAMPARE IL CODICE DELLA TESSERA LETTA SU SERIALE-------------------------------------------------------------------

void stampa_code(byte * code){ 
        Serial.print(": <");
        for (int i=0; i<5; i++) {
          if (code[i] < 16) Serial.print("0");
          Serial.print(code[i], HEX);
          if(i!=4)
            Serial.print(" ");
        }
        Serial.println(">");
}
